package com.audaque.springboot.foshanupload.security.config;

import com.audaque.springboot.foshanupload.authcore.anno.AnonymousAccessAnno;
import com.audaque.springboot.foshanupload.authcore.properties.LoginProperties;
import com.audaque.springboot.foshanupload.core.component.ApplicationContextProvider;
import com.audaque.springboot.foshanupload.core.properties.StaticResourcePathProperties;
import com.audaque.springboot.foshanupload.security.encoder.MyMd5PasswordEncoder;
import com.audaque.springboot.foshanupload.security.service.impl.MyUserDetailService;
import com.audaque.springboot.foshanupload.security.valid.filter.LoginFilter;
import com.audaque.springboot.foshanupload.security.valid.provider.UserVerifyAuthenticationProvider;
import com.audaque.springboot.foshanupload.security.valid.rsp.expire.InvalidSessionHandler;
import com.audaque.springboot.foshanupload.security.valid.rsp.kid.SessionInformationExpiredHandler;
import com.audaque.springboot.foshanupload.security.valid.rsp.login.MyAuthenticationFailureHandler;
import com.audaque.springboot.foshanupload.security.valid.rsp.login.MyAuthenticationSuccessHandler;
import com.audaque.springboot.foshanupload.security.valid.rsp.logout.MyLogoutSuccessHandler;
import com.audaque.springboot.foshanupload.security.valid.rsp.validfail.MyAccessDeniedHandler;
import com.audaque.springboot.foshanupload.security.valid.rsp.validfail.MyAuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author zgb
 * @desc ...
 * @date 2022-08-10 22:55:48
 */

@Configuration
@EnableWebSecurity
//启用spring方法级安全时，使用这个注解
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;


    @Autowired
    private MyLogoutSuccessHandler myLogoutSuccessHandler;


    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;


    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;


    @Autowired
    private MyAuthenticationEntryPoint myAuthenticationEntryPoint;


    @Autowired
    private MyMd5PasswordEncoder myMd5PasswordEncoder;

    @Autowired
    private MyUserDetailService myUserDetailService;



    @Autowired
    private UserVerifyAuthenticationProvider authenticationManager;//认证用户类

    @Autowired
    private LoginProperties loginProperties;

    @Autowired
    private StaticResourcePathProperties staticResourcePathProperties;

    @Autowired
    private ApplicationContextProvider applicationContextProvider;

    /**
     * 超时处理
     */
    @Autowired
    private InvalidSessionHandler invalidSessionHandler;
    /**
     * 顶号处理
     */
    @Autowired
    private SessionInformationExpiredHandler sessionInformationExpiredHandler;



    /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++==*/


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailService).passwordEncoder(myMd5PasswordEncoder);
    }


    /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++==*/


    /**
     * configure(WebSecurity web)不走过滤链的放行
     * 即不通过security 完全对外的/最大级别的放行
     **/

    /*用于影响全局安全性(配置资源，设置调试模式，通过实现自定义防火墙定义拒绝请求)的配置设置。
    一般用于配置全局的某些通用事物，例如静态资源等
    */
    // 直接放行了  如果是login接口 必须通过HttpSecurity 走过滤链 因为登录涉及 SecurityContextHolder
    @Override
    public void configure(WebSecurity web) throws Exception {

        //要排除的路径
        List<String> excludePathPatterns = staticResourcePathProperties.getList();
        String[] arr = excludePathPatterns.toArray(new String[excludePathPatterns.size()]);
        web.ignoring().antMatchers(arr);

        super.configure(web);
    }


    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        //认证方式1
        httpSecurity
                // 登录行为由自己实现，参考 AuthController#login
                .formLogin()

                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET,  //允许对于网站静态资源的无授权访问
                        loginProperties.getUnAuthenticatedUrlList().toArray(new String[loginProperties.getUnAuthenticatedUrlList().size()])
                )
                .permitAll()

                .antMatchers(HttpMethod.OPTIONS)//跨域请求会先进行一次options请求
                .permitAll()


                .requestMatchers(CorsUtils::isPreFlightRequest)
                .permitAll()
                // 对于登录login 验证码captchaImage 允许匿名访问
                .antMatchers(loginProperties.getLoginUrl()).anonymous()
                // 所有加 AnonymousAccessAnno 注解的请求都允许匿名访问
                .antMatchers(getAnonymousUrls()).anonymous()
                //其他所有请求需要登录, anyRequest 不能配置在 antMatchers 前面，一定要配置 .anyRequest().authenticated()，否则全部放行
                .anyRequest().authenticated()

                .and()
                //注入异常处理过滤器
                .exceptionHandling()
                //鉴权失败处理器
                .accessDeniedHandler(myAccessDeniedHandler)
                //鉴证失败处理器
                .authenticationEntryPoint(myAuthenticationEntryPoint)
                .and()
                .logout()
                .logoutUrl(loginProperties.getLogoutUrl()) // 你的登出URL
                .logoutSuccessHandler(myLogoutSuccessHandler) // 指定自定义handler
                .permitAll()
                .and()
                //配置登录过滤器
                .addFilter(new LoginFilter(authenticationManager, myAuthenticationSuccessHandler, myAuthenticationFailureHandler,loginProperties.getLoginUrl()))
                .csrf().disable();


    }


    /**
     * 获取标有注解 AnonymousAccess 的访问路径
     */
    private String[] getAnonymousUrls() {
        // 获取所有的 RequestMapping
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = applicationContextProvider.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
        Set<String> allAnonymousAccess = new HashSet<>();
        // 循环 RequestMapping
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethods.entrySet()) {
            HandlerMethod value = infoEntry.getValue();
            // 获取方法上 AnonymousAccess 类型的注解
            AnonymousAccessAnno methodAnnotation = value.getMethodAnnotation(AnonymousAccessAnno.class);
            // 如果方法上标注了 AnonymousAccess 注解，就获取该方法的访问全路径
            if (methodAnnotation != null) {
                allAnonymousAccess.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
            }
        }
        return allAnonymousAccess.toArray(new String[0]);
    }

}
